{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 模型的使用代码\n",
    "\n",
    "模型训练好了之后要实际应用。对于模型部署有很多成熟的方案，如 Nvidia 的 TensorRT， Intel 的 OpenVINO 等，都可以做模型的高效部署，这里限于篇幅不涉及相关内容。\n",
    "\n",
    "在模型训练过程中，也可以使用使用框架提供的 API 做模型的简单部署以方便开发。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'1.4.0'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "import torchvision as tv\n",
    "import os\n",
    "torch.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首先要加载模型的标签用于展示，因为我们训练的时候就已经生成了标签文件，这里直接用写好的代码就可以。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "if os.path.exists(\"labels.txt\"):\n",
    "    with open(\"labels.txt\") as inf:\n",
    "        classes = [l.strip() for l in inf]\n",
    "else:\n",
    "    classes = os.listdir(\"worddata/train/\")\n",
    "    with open(\"labels.txt\", \"w\") as of:\n",
    "        of.write(\"\\r\\n\".join(classes))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接着是模型的定义，这里直接将训练中使用的模型代码拿来即可。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyModel(torch.nn.Module):\n",
    "    def __init__(self):\n",
    "        super(MyModel, self).__init__()\n",
    "        # 模型有两个主要部分，特征提取层和分类器\n",
    "\n",
    "        # 这里是特征提取层\n",
    "        self.feature = torch.nn.Sequential()\n",
    "        self.feature.add_module(\"conv1\", self.conv(1, 64))\n",
    "        self.feature.add_module(\"conv2\", self.conv(64, 64, add_pooling=True))\n",
    "\n",
    "        self.feature.add_module(\"conv3\", self.conv(64, 128))\n",
    "        self.feature.add_module(\"conv4\", self.conv(128, 128, add_pooling=True))\n",
    "\n",
    "        self.feature.add_module(\"conv5\", self.conv(128, 256))\n",
    "        self.feature.add_module(\"conv6\", self.conv(256, 256))\n",
    "        self.feature.add_module(\"conv7\", self.conv(256, 256, add_pooling=True))\n",
    "\n",
    "        self.feature.add_module(\"conv8\", self.conv(256, 512))\n",
    "        self.feature.add_module(\"conv9\", self.conv(512, 512))\n",
    "        self.feature.add_module(\"conv10\", self.conv(512, 512, add_pooling=True))\n",
    "\n",
    "        self.feature.add_module(\"conv11\", self.conv(512, 512))\n",
    "        self.feature.add_module(\"conv12\", self.conv(512, 512))\n",
    "        self.feature.add_module(\"conv13\", self.conv(512, 512, add_pooling=True))\n",
    "\n",
    "        self.feature.add_module(\"avg\", torch.nn.AdaptiveAvgPool2d((1, 1)))\n",
    "        self.feature.add_module(\"flatten\", torch.nn.Flatten())\n",
    "\n",
    "        self.feature.add_module(\"linear1\", torch.nn.Linear(512, 4096))\n",
    "        self.feature.add_module(\"act_linear_1\", torch.nn.ReLU())\n",
    "        self.feature.add_module(\"bn_linear_1\", torch.nn.BatchNorm1d(4096))\n",
    "\n",
    "        self.feature.add_module(\"linear2\", torch.nn.Linear(4096, 4096))\n",
    "        self.feature.add_module(\"act_linear_2\", torch.nn.ReLU())\n",
    "        self.feature.add_module(\"bn_linear_2\", torch.nn.BatchNorm1d(4096))\n",
    "\n",
    "        self.feature.add_module(\"dropout\", torch.nn.Dropout())\n",
    "\n",
    "        # 这个简单的机构是分类器\n",
    "        self.pred = torch.nn.Linear(4096, 100)\n",
    "\n",
    "    def conv(self, in_channels, out_channels, add_pooling=False):\n",
    "        # 模型大量使用重复模块构建，\n",
    "        # 这里将重复模块提取出来，简化模型构建过程\n",
    "        model = torch.nn.Sequential()\n",
    "        model.add_module(\n",
    "            \"conv\", torch.nn.Conv2d(in_channels, out_channels, 3, padding=1)\n",
    "        )\n",
    "        model.add_module(\"act_conv\", torch.nn.ReLU())\n",
    "        model.add_module(\"bn_conv\", torch.nn.BatchNorm2d(out_channels))\n",
    "\n",
    "        if add_pooling:\n",
    "            model.add_module(\"pool\", torch.nn.MaxPool2d((2, 2)))\n",
    "        return model\n",
    "\n",
    "    def forward(self, x):\n",
    "        # call 用来定义模型各个结构之间的运算关系\n",
    "\n",
    "        x = self.feature(x)\n",
    "        return self.pred(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有了模型的定义之后，我们可以加载训练好的模型，跟模型训练的时候类似，我们可以直接加载模型训练中的 checkpoint。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "model lodaded\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "model = MyModel().cuda()\n",
    "\n",
    "if os.path.exists('ckpts_pth/model_ckpt.pth'):\n",
    "    # 检查 checkpoint 是否存在\n",
    "    # 如果存在，则加载 checkpoint\n",
    "\n",
    "    net_state, optm_state = torch.load('ckpts_pth/model_ckpt.pth')\n",
    "\n",
    "    model.load_state_dict(net_state)\n",
    "\n",
    "    # 这里是一个比较生硬的方式，其实还可以观察之前训练的过程，\n",
    "    # 手动选择准确率最高的某次 checkpoint 进行加载。\n",
    "    print(\"model lodaded\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于数据，我们需要直接处理图片，因此这里导入一些图片处理的库和数据处理的库"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "import numpy as np\n",
    "from PIL import Image"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "直接打开某个图片"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7fef518a3748>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "img = Image.open(\n",
    "    \"worddata/validation/从/116e891836204e4e67659d2b73a7e4780a37c301.jpg\")\n",
    "\n",
    "plt.imshow(img, cmap=\"gray\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "需要注意，模型在训练的时候，我们对数据进行了一些处理，在模型使用的时候，我们要对数据做一样的处理，如果不做的话，模型最终的结果会出现不可预料的问题。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(128, 128)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "img = img.resize((128, 128))\n",
    "img = np.array(img) / 255\n",
    "img.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "模型对图片数据的运算其实很简单，一行代码就可以。\n",
    "\n",
    "> 这里需要注意模型处理的数据是 4 维的，而上面的图片数据实际是 2 维的，因此要对数据进行维度的扩充。同时模型的输出是 2 维的，带 batch ，所以需要压缩一下维度。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[7.042408323165716e-11, 1.551086897810805e-10, 2.2588204917628474e-10, 4.854148372146483e-08, 1.0]\n",
      "['遂', '夜', '御', '作', '从']\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/dl/miniconda3/lib/python3.7/site-packages/ipykernel_launcher.py:4: UserWarning: Implicit dimension choice for softmax has been deprecated. Change the call to include dim=X as an argument.\n",
      "  after removing the cwd from sys.path.\n"
     ]
    }
   ],
   "source": [
    "model.eval()\n",
    "pred = np.squeeze(\n",
    "    model(torch.Tensor(img[np.newaxis, np.newaxis, :, :]).cuda()))\n",
    "pred = torch.nn.functional.softmax(pred)\n",
    "pred.argsort()[-5:]\n",
    "\n",
    "print([pred[idx].item() for idx in pred.argsort()[-5:]])\n",
    "print([classes[idx] for idx in pred.argsort()[-5:]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里只给出了 top5 的结果，可以看到，准确率还是不错的。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
